package com.db.logger;

import com.db.logger.appender.Appender;
import com.db.logger.domain.LoggerEntry;
import com.db.logger.domain.LoggerLevel;
import com.db.logger.layout.Layout;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class LoggerImpl implements Logger {
    private LoggerLevel loggerLevel;
    private Appender appender;
    private Layout layout;
    private BlockingQueue<LoggerEntry> loggerEntryQueue;

    private static final int LOGGER_ENTRY_QUEUE_LENGTH = 1000;

    public LoggerImpl(LoggerLevel loggerLevel, Appender appender, Layout layout, int loggerEntryQueueLength) {
        this.loggerLevel = loggerLevel;
        this.appender = appender;
        this.layout = layout;
        this.loggerEntryQueue = new LinkedBlockingQueue<>(loggerEntryQueueLength);
        runDaemon();
    }

    public LoggerImpl(LoggerLevel loggerLevel, Appender appender, Layout layout) {
        this(loggerLevel, appender, layout, LOGGER_ENTRY_QUEUE_LENGTH);
    }

    @Override
    public boolean isDebugEnabled() {
        return isLevelEnabled(LoggerLevel.DEBUG);
    }

    @Override
    public boolean isInfoEnabled() {
        return isLevelEnabled(LoggerLevel.INFO);
    }

    @Override
    public boolean isErrorEnabled() {
        return isLevelEnabled(LoggerLevel.ERROR);
    }

    @Override
    public void debug(String message, Object... args) {
        log(LoggerLevel.DEBUG, message, args);
    }

    @Override
    public void info(String message, Object... args) {
        log(LoggerLevel.INFO, message, args);
    }

    @Override
    public void error(String message, Object... args) {
        log(LoggerLevel.ERROR, message, args);
    }

    @Override
    public void log(LoggerLevel loggerLevel, String message, Object... args) {
        loggerEntryQueue.add(new LoggerEntry(loggerLevel, message, args));
    }

    private void runDaemon() {
        Thread daemon = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        processLoggerEntry(loggerEntryQueue.take());
                    } catch (InterruptedException e) {
                        throw new RuntimeException("Can not process logger entry");
                    }
                }
            }
        });
        daemon.setDaemon(true);
        daemon.start();
    }

    private void processLoggerEntry(LoggerEntry loggerEntry) {
        if (isLevelEnabled(loggerEntry.getLoggerLevel())) {
            appender.write(layout.format(loggerEntry));
        }
    }

    private boolean isLevelEnabled(LoggerLevel loggerLevel) {
        return (loggerLevel.getSeverity() >= this.loggerLevel.getSeverity());
    }
}
